<?php
/**
 * @author Jumper Swordwave
 * date: 2021/04/19
 */
declare(strict_types=1);
namespace App\Game\Service;

use Psr\Container\ContainerInterface;
use App\Game\Logic\PokerHelper;
use App\Game\Model\{Room, User};
use App\Game\Core\GameStatus;

/**
 * 房间服务
 */
class RoomService extends BaseService
{
    /**
     * @var integer
     * 最大房间数量
     */
    private $max_room;

    /**
     * 房间缓存id
     */
    private $room_key;

    /**
     * 空间房间列表
     */
    private $idle_room_key;
    
    /**
     * 游戏中房间列表
     */
    private $busy_room_key;

    /**
     * 房间id生成
     */
    private $id_generate_key;

    public function __construct(ContainerInterface $container)
    {
        parent::__construct($container);
        $this->room_key = config('game.key.room_info', 'room:info:%d');
        $this->idle_room_key = config('game.key.idle_room', 'room:idle:list');
        $this->busy_room_key = config('game.key.busy_room', 'room:busy:list');
        $this->id_generate_key = config('game.key.room_id', 'room:generate:key');
        $this->max_room = intval(config('game.ddz.max_room', 50));
        $this->redis->setnx($this->id_generate_key, 1);
    }

    /**
     * 查找房间
     * @param int   $id 
     * @return ?Room
     */
    public function findRoom(int $id) : ?Room
    {
        return $this->get(sprintf($this->room_key, $id));
    }
    
    /**
     * 保存房间
     * @param Room  $room
     */
    public function saveRoom(Room $room)
    {
        $this->save(sprintf($this->room_key, $room->id), $room);
    }

    /**
     * 新建房间
     * @return Room
     */
    public function newRoom() : Room
    {
        $room = new Room([
            'id' => $this->redis->incr($this->id_generate_key),
            'status' => GameStatus::ROOM_WAITING,
            'master' => 0,
            'current' => 0,
            'score' => 0,
            'limit' => config('game.ddz.room_limit', 3),
        ]);
        $this->saveRoom($room);
        return $room;
    }

    /**
     * 更新房间信息
     * @param Room|int   $room_or_id
     * @param array $data 
     * @return ?Room
     */
    public function updateRoom($room_or_id, array $data) : ?Room 
    {
        $room = $room_or_id;
        if (is_numeric($room_or_id)) {
            $room = $this->findRoom(intval($room_or_id));
        }
        if ($room == null) return null;

        $room->fill($data);
        $this->saveRoom($room);
        return $room;
    }

    /**
     * 空闲房间数量
     */
    public function idle_count()
    {
        return $this->redis->scard($this->idle_room_key);
    }
    
    /**
     * 游戏中房间数量
     */
    public function busy_count()
    {
        return $this->redis->scard($this->busy_room_key);
    }
    
    /**
     * 清除房间状态
     */
    public function clear()
    {
        $idles = $this->redis->smembers($this->idle_room_key);
        foreach($idles as $_id) {
            $this->redis->del(sprintf($this->room_key, $_id));
        }
        $busys = $this->redis->smembers($this->busy_room_key);
        foreach($busys as $_id) {
            $this->redis->del(sprintf($this->room_key, $_id));
        }
        $this->redis->del($this->idle_room_key);
        $this->redis->del($this->busy_room_key);
        $this->redis->set($this->id_generate_key, 1);
    }

    /**
     * 查找空闲房间
     */
    public function searchRoom() : ?Room 
    {
        $idle_count = $this->redis->scard($this->idle_room_key);
        if ($idle_count > 0) {
            $room_id = $this->redis->srandmember($this->idle_room_key);
            $room = $this->findRoom(intval($room_id));
            if ($room) {
                return $room;
            }
        }
        $busy_count = $this->redis->scard($this->busy_room_key);
        if ($busy_count >= $this->max_room) {   //房间已满
            return null;
        }
        
        //新建房间
        $room = $this->newRoom();
        $this->redis->sadd($this->idle_room_key, $room->id);
        return $room;
    }
    
    /**
     * 用户加入房间
     * @param Room  $room 
     * @param User  $user
     * @return bool 加入是否成功
     */
    public function userJoin(Room $room, User $user) : bool
    {
        $position = $room->join($user->id);
        $this->log->info('user join', ['id' => $user->id, 'room' => $room->id, 'position'=>$position, 'users'=>$room->users]);
        if ($position == 0) {
            return false;
        }
        
        //用户加入房间,更新为等待状态
        $userService = $this->container->get(UserService::class);
        $userService->updateUser($user, [
            'room' => $room->id,
            'status' => GameStatus::USER_READY,
            'position' => $position,
        ]);
        $this->saveRoom($room);

        if ($room->isFull()) {  //房间用户满
            $this->redis->srem($this->idle_room_key, $room->id);
            $this->redis->sadd($this->busy_room_key, $room->id);
        }
        return true;
    }

    /**
     * 用户离开房间
     * @param Room  $room
     * @param User  $user 
     * @return bool 离开是否成功
     */
    public function userLeave(Room $room, User $user) : bool
    {
        $userService = $this->container->get(UserService::class);
        if ($room->leave($user->id)) {
            $userService->leave($user);
            $this->saveRoom($room);

            //将房间从进行中队列移到空闲列队中
            $this->redis->srem($this->busy_room_key, $room->id);
            $this->redis->sadd($this->idle_room_key, $room_id);
            return true;
        }
        
        return false;
    }

    /**
     * 是否所有玩家都已就绪
     * @param Room  $room 
     * @return bool
     */
    public function allReady(Room $room) : bool
    {
        if (! $room->isFull()) return false;    //游戏玩家未满

        $userService = $this->container->get(UserService::class);
        $users = $userService->findUsers($room->users);
        foreach($users as $user) {
            if (!$user->isReady()) return false;
        }

        return true;
    }

    /**
     * 游戏开始,发牌
     */
    public function gameStart(Room $room) : ?Room
    {
        if (! $this->allReady($room)) return null;
        
        $userService = $this->container->get(UserService::class);
        //准备就绪,开始发牌
        $room->status = GameStatus::ROOM_READY;
        $this->saveRoom($room);
        $users = $userService->findUsers($room->users);
        foreach($users as &$user) {
            $user->status = GameStatus::USER_PLAYING;
        }
        $userService->saveUsers($users);
        //发牌,并进入叫地主状态
        list($hands, $holes) = PokerHelper::send();
        $room->initCards($hands, $holes);
        $this->saveRoom($room);

        return $room;
    }

    /**
     * 叫地主阶段,用户叫地主
     * @param Room  $room   房间
     * @param User  $user   叫地主用户
     * @param bool  $call   是否抢地主
     */
    public function gameCall(Room $room, User $user, bool $call) : ?Room
    {
        $pos = $room->getUserPosition($user->id);
        \App\Log::info('game call', ['user' => $user->toArray(), 'pos' => $pos]);
        $result = $room->call($pos, $call);
        if ($result) {  //叫地主成功
            $this->saveRoom($room);
            return $room;
        }
        
        return null;
    }

    /**
     * 玩家出牌
     */
    public function outCards(Room $room, User $user, array $cards) : ?Room
    {
        $pos = $room->getUserPosition($user->id);
        if (empty($cards)) {    //玩家不出牌
            if ($room->isFirstOut($pos)) {  //如果是起手出牌玩家,不能跳过
                return null;
            }
            $result = $room->skip($pos);
        } else {    //玩家出牌
            $result = $room->outs($pos, $cards, function($cCards, $pCards) {
                //出牌逻辑
                return PokerHelper::checkOuts($cCards, $pCards);
            });
        }

        if ($result) {
            $this->saveRoom($room);
            return $room;
        }
        return null;
    }

    /**
     * 游戏结束
     * @param Room  $room
     * @return Room|null
     */
    public function gameOver(Room $room) : ?Room
    {
        $win_id = $room->winUser();
        if ($win_id == 0) return null;

        $userService = $this->container->get(UserService::class);
        $users = $userService->findUsers($room->users);
        foreach($users as $user) {
            $user->status = GameStatus::USER_GAMEOVER;
        }
        $userService->saveUsers($users);

        $room->gameover();
        $this->saveRoom($room);
        return $room;
    }
}